package marcis.jarina.directory.repository;

import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 * For working with xml files especially poms.
 *
 * @author jan.marcis
 */
public class XMLUtil {

    private static final Logger log = Logger.getLogger("marcis.jarina.XMLUtil");

    private Document doc;
    private final File file;
    private XPath xpath;
    private List<Dependency> dependencyCache;

    public XMLUtil(File fajl) throws XMLFileException {
        this.file = fajl;
        if (file == null) {
            throw new XMLFileException("Pom file is NULL!");
        }
        log.debug("using file " + fajl.getName());
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            doc = builder.parse(file);
            XPathFactory xpFactory = XPathFactory.newInstance();
            xpath = xpFactory.newXPath();
        } catch (ParserConfigurationException e) {
            throw new XMLFileException("Error configuring parser while parsing file: " + file.getPath(), e.getCause());
        } catch (SAXException e) {
            throw new XMLFileException("Error parsing XML file: " + file.getPath(), e.getCause());
        } catch (IOException e) {
            throw new XMLFileException("Error working with file: " + file.getPath(), e.getCause());
        }
    }

    /**
     * save user project pom.xml file.
     */
    public void saveXMLDocument() {
        try {
            log.info("saving file...");
            XMLSerializer serializer = new XMLSerializer();
            serializer.setOutputCharStream(new FileWriter(file));
            serializer.serialize(doc);
        } catch (IOException e) {
            log.error("Unable to save xml file!");
            throw new RuntimeException("Unable to save pom file", e);
        }
    }

    private List<Node> getNodesByXpath(String xpathString) throws XMLFileException {
        List<Node> result = new ArrayList<Node>();
        XPathExpression expr = null;
        Object xpResult = null;
        try {
            expr = xpath.compile(xpathString);
            xpResult = expr.evaluate(doc, XPathConstants.NODESET);
            NodeList nodes = (NodeList) xpResult;
            for (int i = 0; i < nodes.getLength(); i++) {
                result.add(nodes.item(i));
            }
        } catch (XPathExpressionException e) {
            log.error("xpath is broken");
        }
        if (result.size() == 0) {
            throw new XMLFileException("Can't retrieve anything for xpath: " + xpathString);
        }
        return result;
    }

    public List<Dependency> getALLPomDependencies() throws XMLFileException {
        if (dependencyCache == null) {
            dependencyCache = new ArrayList<Dependency>();
            //List<Node> nodeDeps = getNodesByXpath("//project/dependencies/dependency");
            List<Node> nodeDeps = getNodesByXpath("//dependency");
            for (Node node : nodeDeps) {
                String gId = null;
                String aId = null;
                String v = null;

                NodeList nodeList = node.getChildNodes();
                for (int i = 0; i < nodeList.getLength(); i++) {
                    String nodeName = nodeList.item(i).getNodeName();
                    if ("groupId".equals(nodeName)) {
                        gId = nodeList.item(i).getTextContent();
                    } else if ("artifactId".equals(nodeName)) {
                        aId = nodeList.item(i).getTextContent();
                    } else if ("version".equals(nodeName)) {
                        v = nodeList.item(i).getTextContent();
                    }
                }
                Dependency dependency = new Dependency();
                dependency.setGroupId(gId);
                dependency.setArtifactId(aId);
                dependency.setVersion(v);
                dependencyCache.add(dependency);
            }
        }
        return dependencyCache;
    }


    public void addDependenciesToPomFile(List<Dependency> dependencies) throws XMLFileException {
        for (Dependency dependency : dependencies) {
            addDependency(dependency);
        }
    }

    /**
     * @return group id of project pom file.
     * @throws XMLFileException
     */
    public String getGroupId() throws XMLFileException {
        try {
            return getNodesByXpath("//project/groupId").get(0).getTextContent();
        } catch (XMLFileException e) {
            // in case that not found -try parent. otherwise throw out..
            return getNodesByXpath("//project/parent/groupId").get(0).getTextContent();
        }
    }

    /**
     * @return artifact id of project pom file.
     * @throws XMLFileException
     */
    public String getArtifactId() throws XMLFileException {
        return getNodesByXpath("//project/artifactId").get(0).getTextContent();
    }

    /**
     * @return version of project pom file.
     * @throws XMLFileException
     */
    public String getVersion() throws XMLFileException {
        try {
            return getNodesByXpath("//project/version").get(0).getTextContent();
        } catch (XMLFileException e) {
            // in case that not found -try parent. otherwise throw out..
            return getNodesByXpath("//project/parent/version").get(0).getTextContent();
        }
    }

    public void addDependency(Dependency dependency) throws XMLFileException {
        log.debug("adding dependency: " + dependency.toString());

        Element newChild = doc.createElement("dependency");

        Element gr = doc.createElement("groupId");
        gr.setTextContent(dependency.getGroupId());
        Element ar = doc.createElement("artifactId");
        ar.setTextContent(dependency.getArtifactId());
        Element ver = doc.createElement("version");
        ver.setTextContent(dependency.getVersion());

        newChild.appendChild(gr);
        newChild.appendChild(ar);
        newChild.appendChild(ver);

        Node dependenciesNode = getNodesByXpath("//project/dependencies").get(0);
        dependenciesNode.appendChild(newChild);
    }


    @Override
    public String toString() {
        try {
            return getGroupId() + ":" + getArtifactId() + ":" + getVersion();
        } catch (XMLFileException e) {
            return "Pom file broken! " + e.getMessage();
        }
    }

    public File getFile() {
        return file;
    }

}
